package com.tmt.paymentcenter.web.configure.security.bean;

import com.tmt.paymentcenter.web.vo.HttpResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.oauth2.client.DefaultOAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2ClientContext;
import org.springframework.security.oauth2.client.OAuth2RestOperations;
import org.springframework.security.oauth2.client.OAuth2RestTemplate;
import org.springframework.security.oauth2.client.token.AccessTokenRequest;
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest;
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsResourceDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import tmt.usercenter.web.domain.security.UserDetailsImpl;

import javax.servlet.http.HttpSession;
import java.text.MessageFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

@Service
@Transactional
public class TmtClientSessionRegistry extends SessionRegistryImpl {

    @Value("${security.oauth2.client.check-token-uri}")
    private String checkTokenUri;

    @Value("${security.oauth2.client.logout-notify-uri}")
    private String logoutNotifyUri;

    @Autowired
    private ClientResources oauth2Client;

    @Qualifier("oauth2ClientContext")
    @Autowired
    private OAuth2ClientContext oauth2ClientContext;

    private ConcurrentHashMap<HttpSession, Object> tokenSessionMap = new ConcurrentHashMap<>();

    public void addSession(Object token, HttpSession session, Authentication authentication) {
        if (session != null)
            if (tokenSessionMap.keySet().stream().noneMatch(x -> Objects.equals(x.getId(), session.getId()))) {
                tokenSessionMap.put(session, token == null ? "" : token);
            }
    }

    public ConcurrentHashMap<HttpSession, Object> getTokenSessionMap(){
        return tokenSessionMap;
    }

    @Override
    public void removeSessionInformation(String sessionId) {
        removeSessionInformation(sessionId, true);
    }


    @Async
    public synchronized void userLogout(Long userId, boolean sendNotifyToAuthServer){
        String clientId = oauth2Client.getClient().getClientId();
        this.getAllPrincipals().stream()
                .filter(x-> Objects.equals(((UserDetailsImpl) x).getUserId(), userId)
                        && (clientId == null || Objects.equals(((UserDetailsImpl) x).getClientId(), clientId) )).forEach(x->{
            UserDetailsImpl details = (UserDetailsImpl)x;
            expireSession(details, sendNotifyToAuthServer);
        });
    }


    private HttpSession findSessionById(String sessionId){
        Optional<HttpSession> optional = tokenSessionMap.keySet()
                .parallelStream().filter(x-> Objects.equals(x.getId(), sessionId)).findFirst();
        return optional.orElse(null);
    }

    private void expireSession(UserDetailsImpl details, boolean sendNotifyToAuthServer){
        List<SessionInformation>  sessionInformations = this.getAllSessions(details, false);
        sessionInformations.stream().forEach(y->{
            y.expireNow();
            removeSessionInformation(y.getSessionId(), sendNotifyToAuthServer);
            try {
                HttpSession session = this.findSessionById(y.getSessionId());
                SecurityContext sc = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
                sc.setAuthentication(null);
            }catch (Exception ex){}
        });
    }

    private void removeSessionInformation(String sessionId, boolean sendNotifyToAuthServer) {
        HttpSession session = findSessionById(sessionId);
        if (session == null)
            return;

        try {
            if (sendNotifyToAuthServer) {
                //通知Oauth2服务器，当前用户登出了。
                SecurityContext sc = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
                UserDetailsImpl userDetails = (UserDetailsImpl) (sc.getAuthentication().getPrincipal());
                broadcastLogoutNotify(userDetails.getUserId());
            }
        }catch (Exception ex){
            ex.printStackTrace();
        }

        tokenSessionMap.remove(session);
        session.invalidate();

        super.removeSessionInformation(sessionId);
    }

    /**
     * 广播登出通知
     */
    private boolean broadcastLogoutNotify(long userId){
        //通知Oauth2服务器，当前用户登出了。
        OAuth2AccessToken accessToken = oauth2ClientContext.getAccessToken();
        String url = String.format(logoutNotifyUri, userId, accessToken.getValue());
        OAuth2RestTemplate template = new OAuth2RestTemplate(oauth2Client.getClient(), new DefaultOAuth2ClientContext());
        HttpResult result = template.getForObject(url, HttpResult.class);
        logger.debug("--------------broadcastLogoutNotify-------------" + result.toString());
        return result.isSuccess();
    }


    /**
     * 超时事件检查
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void ScanToken() {
        System.out.println("---------ScanToken--------------");

        tokenSessionMap.keySet().stream().forEach(x->{
            String accessToken = (String) tokenSessionMap.get(x);
            if ("".equals(accessToken.trim()))
                return;

            /*Map<String, ?> map = checkAccessToken(accessToken);
            if (map == null || map.isEmpty()){
                try {
                    //访问令牌失效处理: 强制登出。
                    SecurityContext sc = (SecurityContext) x.getAttribute("SPRING_SECURITY_CONTEXT");
                    UserDetailsImpl userDetails = (UserDetailsImpl) (sc.getAuthentication().getPrincipal());
                    userLogout(userDetails.getUserId(), false);
                }catch (Exception ex){}
            }*/
        });
    }

    private Map<String, ?> checkAccessToken(String accessToken) {
        Map<String, ?> result = new HashMap<>();
        try {
            String url = MessageFormat.format( "{0}?token={1}" , checkTokenUri, accessToken);
            RestTemplate template = new RestTemplate();
            Map<String, ?> response = template.getForObject(url, Map.class);
            response.keySet().stream().forEach(x->{
                String value = response.get(x).toString();
                System.out.println(MessageFormat.format("----{0}-----{1}---", x, value));
            });
            result = response;
        }catch (Exception ex){
            ex.printStackTrace();
        }
        return result;
    }

}
